#include "intercode.h"

int labelNum = 0, varNum = 0, tmpNum = 0;

interCode *IChead = NULL, *ICtail = NULL;

operand nullOpr = {EOPR, 0, 0};
operand oneOpr = {CONSTANT, 1, 0};
operand zeroOpr = {CONSTANT, 0, 0};

int getLabelNum()
{
    return ++labelNum;
}

int getVarNum()
{
    return ++varNum;
}

int getTmpNum()
{
    return ++tmpNum;
}

operand newOpr(int type, int value)
{
    operand ret;
    ret.type = type;
    switch(type){
        case VARIABLE:
            ret.varID = value; break;
        case CONSTANT:
            ret.constant = value; break;
        case TEMP:
            ret.tmpID = value; break;
    }
    ret.addr = 0;
    return ret;
}

interCode* newIC(int type)
{
    interCode* ret = (interCode*)malloc(sizeof(interCode));
    ret->type = type;
    ret->prev = ret;
    ret->next = ret;
    return ret;
}

interCode* newLabelIC(int labelID)
{
    interCode* ret = newIC(LABEL);
    ret->labelID = labelID;
    return ret;
}

interCode* newGotoIC(int labelID)
{
    interCode* ret = newIC(GOTO);
    ret->labelID = labelID;
    return ret;
}

interCode* newFunctionIC(const char* funcName)
{
    interCode* ret = newIC(FUNCTION);
    strcpy(ret->funcName, funcName);
    return ret;
}

interCode* newReturnIC(operand opr)
{
    interCode* ret = newIC(RETURNIC);
    ret->opr = opr;
    return ret;
}

interCode* newArgIC(operand opr)
{
    interCode* ret = newIC(ARG);
    ret->opr = opr;
    return ret;
}

interCode* newParamIC(operand opr)
{
    interCode* ret = newIC(PARAM);
    ret->opr = opr;
    return ret;
}

interCode* newReadIC(operand opr)
{
    interCode* ret = newIC(READ);
    ret->opr = opr;
    return ret;
}

interCode* newWriteIC(operand opr)
{
    interCode* ret = newIC(WRITE);
    ret->opr = opr;
    return ret;
}

interCode* newAssignIC(operand dst, operand src1, operand src2, int opType)
{
    interCode* ret = newIC(ASSIGN);
    ret->assign.dst = dst;
    ret->assign.src1 = src1;
    ret->assign.src2 = src2;
    ret->assign.opType = opType;
    return ret;
}

interCode* newCondIC(operand opr1, operand opr2, int opType, int labelID)
{
    interCode* ret = newIC(COND);
    ret->cond.opr1 = opr1;
    ret->cond.opr2 = opr2;
    ret->cond.opType = opType;
    ret->cond.labelID = labelID;
    return ret;
}

interCode* newDecIC(int varID, int size)
{
    interCode* ret = newIC(DEC);
    ret->dec.varID = varID;
    ret->dec.size = size;
    return ret;
}

interCode* newCallIC(operand dst, const char* funcName)
{
    interCode* ret = newIC(CALL);
    ret->call.dst = dst;
    strcpy(ret->call.funcName, funcName);
    return ret;
}

void insertIC(interCode* target)
{
    if(!IChead)
    {
        IChead = ICtail = target;
    }
    else
    {
        ICtail->next = target;
        target->prev = ICtail;
        target->next = IChead;
        IChead->prev = target;
        ICtail = target;
    }
}

interCode* mergeIC(interCode* IC1, interCode* IC2)
{
    if(!IC1) return IC2;
    else if(!IC2) return IC1;
    else{
        interCode* IC2tail = IC2->prev;
        IC1->prev->next = IC2;
        IC2->prev = IC1->prev;
        IC2tail->next = IC1;
        IC1->prev = IC2tail;
        return IC1;
    }
}

void operandToString(char* buffer, operand opr) 
{
    assert(buffer);
    buffer[0] = 0;
    switch (opr.type) {
        case EOPR:
            break;
        case CONSTANT:
            sprintf(buffer, "#%d", opr.constant);
            break;
        case VARIABLE:
            sprintf(buffer, "v%d", opr.varID);
            break;
        case TEMP:
            sprintf(buffer, "t%d", opr.tmpID);
            break;
        default:
            assert(0);
    }
}

void printSingleCode(interCode* code)
{
    char buf[256];
    interCodeToString(buf, code);
    printf("%s\n", buf);
}

void interCodeToString(char* buffer, interCode* code) 
{
    assert(buffer);
    buffer[0] = 0;

    char opr1_buffer[32];  // get operand1 string
    char opr2_buffer[32];  // get operand2 string
    switch (code->type) {
        case EIC:
            break;
        case FUNCTION:
            sprintf(buffer, "FUNCTION %s :", code->funcName);
            break;
        case LABEL:
            sprintf(buffer, "LABEL label%d :", code->labelID);
            break;
        case GOTO:
            sprintf(buffer, "GOTO label%d", code->labelID);
            break;
        case COND:
            operandToString(opr1_buffer, code->cond.opr1);
            operandToString(opr2_buffer, code->cond.opr2);
            switch (code->cond.opType) {
                case EQ:
                    sprintf(buffer, "IF %s == %s GOTO label%d", opr1_buffer,
                            opr2_buffer, code->cond.labelID);
                    break;
                case NE:
                    sprintf(buffer, "IF %s != %s GOTO label%d", opr1_buffer,
                            opr2_buffer, code->cond.labelID);
                    break;
                case LE:
                    sprintf(buffer, "IF %s <= %s GOTO label%d", opr1_buffer,
                            opr2_buffer, code->cond.labelID);
                    break;
                case LT:
                    sprintf(buffer, "IF %s < %s GOTO label%d", opr1_buffer,
                            opr2_buffer, code->cond.labelID);
                    break;
                case GE:
                    sprintf(buffer, "IF %s >= %s GOTO label%d", opr1_buffer,
                            opr2_buffer, code->cond.labelID);
                    break;
                case GT:
                    sprintf(buffer, "IF %s > %s GOTO label%d", opr1_buffer,
                            opr2_buffer, code->cond.labelID);
                    break;
                default:
                    assert(0);
            }
            break;
        case PARAM:
            operandToString(opr1_buffer, code->opr);
            sprintf(buffer, "PARAM %s", opr1_buffer);
            break;
        case ARG:
            operandToString(opr1_buffer, code->opr);
            sprintf(buffer, "ARG %s", opr1_buffer);
            break;
        case DEC:
            sprintf(buffer, "DEC v%d %d", code->dec.varID, code->dec.size);
            break;
        case CALL:
            operandToString(opr1_buffer, code->call.dst);
            sprintf(buffer, "%s := CALL %s", opr1_buffer, code->call.funcName);
            break;
        case RETURNIC:
            operandToString(opr1_buffer, code->opr);
            sprintf(buffer, "RETURN %s", opr1_buffer);
            break;
        case READ:
            operandToString(opr1_buffer, code->opr);
            sprintf(buffer, "READ %s", opr1_buffer);
            break;
        case WRITE:
            operandToString(opr1_buffer, code->opr);
            sprintf(buffer, "WRITE %s", opr1_buffer);
            break;
        case ASSIGN:
            operandToString(opr1_buffer, code->assign.src1);
            char dst_buffer[16];
            operandToString(dst_buffer, code->assign.dst);
            switch (code->assign.opType) {
                case AS:
                    sprintf(buffer, "%s := %s", dst_buffer, opr1_buffer);
                    break;
                case ADD:
                    operandToString(opr2_buffer, code->assign.src2);
                    sprintf(buffer, "%s := %s + %s", dst_buffer, opr1_buffer,
                            opr2_buffer);
                    break;
                case SUB:
                    operandToString(opr2_buffer, code->assign.src2);
                    sprintf(buffer, "%s := %s - %s", dst_buffer, opr1_buffer,
                            opr2_buffer);
                    break;
                case MUL:
                    operandToString(opr2_buffer, code->assign.src2);
                    sprintf(buffer, "%s := %s * %s", dst_buffer, opr1_buffer,
                            opr2_buffer);
                    break;
                case DIV:
                    operandToString(opr2_buffer, code->assign.src2);
                    sprintf(buffer, "%s := %s / %s", dst_buffer, opr1_buffer,
                            opr2_buffer);
                    break;
                case ADDR:
                    // Special Case: x := &y + z is allowed
                    if (code->assign.src2.type == EOPR)
                        sprintf(buffer, "%s := &%s", dst_buffer, opr1_buffer);
                    else {
                        operandToString(opr2_buffer, code->assign.src2);
                        sprintf(buffer, "%s := &%s + %s", dst_buffer,
                                opr1_buffer, opr2_buffer);
                    }
                    break;
                case RSTAR:
                    sprintf(buffer, "%s := *%s", dst_buffer, opr1_buffer);
                    break;
                case LSTAR:
                    sprintf(buffer, "*%s := %s", dst_buffer, opr1_buffer);
                    break;
                case LRSTAR:
                    sprintf(buffer, "*%s := *%s", dst_buffer, opr1_buffer);
                    break;
                default:
                    assert(0);
            }
            break;
        default:
            assert(0);
    }
}

void interCodeToFile(interCode* codes, FILE* fp)
{
    if(!codes) return;
    interCode* itr = codes;
    char buffer[256];
    do {
        interCodeToString(buffer, itr);
        fputs(buffer, fp);
        fputc('\n', fp);
        itr = itr->next;
    } while (itr != codes);
}

void printIC(interCode* codes)
{
    if(!codes) {DEBUG("no codes\n"); return;}
    interCode* itr = codes;
    char buffer[256];
    do {
        interCodeToString(buffer, itr);
        DEBUG("%s\n",buffer);
        itr = itr->next;
    } while (itr != codes);
}

